
/*    This file is part of the managarm operating system.
 *   Copyright (C) 2007, 2008, 2009  Alexander van der Grinten
 *
 *   This program is free software: you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation, either version 3 of the License, or
 *   (at your option) any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program.  If not, see <http://www.gnu.org/licenses/>. */

package org.managarm.jmanacclib.frontend.korona;

import java.util.LinkedList;
import java.util.HashMap;
import java.util.Iterator;

import org.managarm.jmanacclib.core.CclibUnit;
import org.managarm.jmanacclib.core.CclibException;
import org.managarm.jmanavalib.core.VaClass;
import org.managarm.jmanavalib.core.VaClsInfo;
import org.managarm.jmanavalib.core.VaDatatype;
import org.managarm.jmanavalib.core.VaFldInfo;
import org.managarm.jmanavalib.core.VaLiteral;
import org.managarm.jmanavalib.core.VaMthInfo;

// import managarm virtual architecture classes

import ast.AstClass;
import ast.AstField;
import ast.AstGeneric;
import ast.AstInstance;
import ast.AstRoot;
import ast.AstSpace;
import ast.AstSymbol;
import ast.AstTerm;
import ast.AstArray;
import ast.AstType;
import ast.AstArrayType;
import ast.AstDelegate;

public class KorClsCompiler {
	// class we have to compile
	private final AstClass classNode;
	// root of the syntax tree
	private final AstRoot root;
	
	// generic patterns this class implements
	private final HashMap<String, VaDatatype> patternMap
			= new HashMap<String, VaDatatype>();
	private final LinkedList<VaDatatype> patternList
			= new LinkedList<VaDatatype>();
	
	// image compiler that is used to compile the class
	private final KorCompiler imageCmp;
	
	// generated class information struct
	private final VaClsInfo clsInfo;
	// generated class body
	private VaClass virtClass;
	
	// constructs a class compiler to compile a usual (not a generic) class
	public KorClsCompiler(final KorCompiler imgcmp,
			final AstRoot root, final AstClass cls) {
		this.imageCmp = imgcmp;
		this.root = root;
		this.classNode = cls;
		
		// create a class description for the class
		clsInfo = describeClass();
	}
	
	// constructs a class compiler to compile an instance of a generic class
	public KorClsCompiler(final KorCompiler imgcmp, final AstRoot root,
			final AstGeneric generic, final VaDatatype[] patterns) {
		this.imageCmp = imgcmp;
		this.root = root;
		this.classNode = generic.getBody();
		
		for(int i = 0; i < patterns.length; i++) {
			final String ident = generic.getPattern(i);
			patternMap.put(ident, patterns[i]);
			patternList.add(patterns[i]);
		}
		
		// create a class description of this class
		clsInfo = describeClass();
	}
	
	// some getter functions
	public final String getIdent() { return clsInfo.ident; }
	public final VaClsInfo getClsInfo() { return clsInfo; }
	public final AstClass getAstNode() { return classNode; }
	public final VaDatatype getPattern(final String ident) {
		return patternMap.get(ident);
	}
	
	// returns the class body. compiles the class
	// if that has not been done before
	public final VaClass getClassBody() throws CclibException {
		if(virtClass == null)
			compile();
		return virtClass;
	}
	
	// resolves a type symbol
	public final VaDatatype typeResolve(final KorAstNode node) throws CclibException {
		if(node instanceof AstType) {
			final AstType type = (AstType)node;
			
			// get the flags
			final int flags1 = type.getFlags1();
			
			final VaDatatype subtype = typeResolve(type.getChild());

			if(subtype instanceof VaDatatype.DtPrimitive) {
				return subtype;
			}else if(subtype instanceof VaDatatype.DtClass) {
				final VaDatatype.DtClass base = (VaDatatype.DtClass)subtype;
				
				return new VaDatatype.DtClass(base.clsInfo);
			}else if(subtype instanceof VaDatatype.DtArray) {
				final VaDatatype.DtArray base = (VaDatatype.DtArray)subtype;
				int arflg1 = 0;
				
				// process the flags
				if((flags1 & AstType.FLAG1_LOCAL) != 0) {
					arflg1 |= VaDatatype.DtArray.FLAG0_LOCAL;
				}else if((flags1 & AstType.FLAG1_UNMANAGED) != 0) {
					arflg1 |= VaDatatype.DtArray.FLAG0_UNMANAGED;
				}else if((flags1 & AstType.FLAG1_XNAT_REFCOUNTED) != 0) {
					arflg1 |= VaDatatype.DtArray.FLAG0_XNAT_REFCOUNTED;
				}else{
					throw new CclibException("Compiler error: " +
						"Unknown type modifier",
						(CclibUnit)classNode.getAttribute("unit"), node.line());
				}
				
				return new VaDatatype.DtArray(base.basetype, arflg1);
			}

			throw new CclibException("Compiler error: " +
					"Could not process type modifier",
					(CclibUnit)classNode.getAttribute("unit"), node.line());
		}else if(node instanceof AstArrayType) {
			final AstArrayType type = (AstArrayType)node;
			
			final VaDatatype basetype = typeResolve(type.basetype);
			
			return new VaDatatype.DtArray(basetype, 0);
		}else if(node instanceof AstTerm) {
			// resolve primitives
			final AstTerm term = (AstTerm)node;
			final String ident = term.getIdentifier();
			
			// is it a primitive type?
			final VaDatatype prim = KorUtils.primResolve(ident);
					
			// if it is a primitive type, return it
			if(prim != null) {
				if(term.getChild() != null)
					return resolveSymbol(prim, term.getChild());
				
				return prim;
			}
			
			// is it a generic pattern?
			final VaDatatype insttype = patternMap.get(ident);
			if(insttype != null) {
				// resolve sub symbols
				if(term.getChild() != null)
					return resolveSymbol(insttype, term.getChild());
				
				// return the pattern type
				return insttype;
			}
			
			// it may be part of the ast root
			try {
				return resolveClass(node, root);
			} catch(KorUtils.UnresolvedException e) { }
			
			// it may be part of the current namespace
			try {
				return resolveClass(node, classNode.getContainer());
			} catch(KorUtils.UnresolvedException e) { }
			
			// it may be part of a namespace that is importet by the current class
			final Iterator<AstTerm> iter = classNode.importIter();
			while(iter.hasNext()) {
				// get the next import
				final AstTerm include = iter.next();
				
				// resolve the space
				final AstSpace space = KorUtils.spaceResolve(include, classNode, root);

				if(space == null)
					throw new CclibException("Compiler error: " +
							"Could not resolve " + term.getIdentifier(),
							(CclibUnit)classNode.getAttribute("unit"),
							node.line());
				
				try {
					return resolveClass(node, space);
				} catch(KorUtils.UnresolvedException e) { }
			}
			
			throw new CclibException("Compiler error: Could not resolve "
					+ term.getIdentifier(),
					(CclibUnit)classNode.getAttribute("unit"), node.line());
		}else if(node instanceof AstDelegate) {
			final AstDelegate dnode = (AstDelegate)node;
			final int argc = dnode.argCount();
			
			// resolve the arguments
			final VaDatatype[] args = new VaDatatype[argc];
			final Iterator<KorAstNode> iter = dnode.argIter();
			int i = 0;
			while(iter.hasNext()) {
				args[i] = typeResolve(iter.next());
				i++;
			}
			
			// resolve the return type
			final VaDatatype rettype = typeResolve(dnode.getRetType());
			
			return new VaDatatype.DtDelegate(rettype, args);
		}
		
		throw new CclibException("Compile error: Could not resolve the node "
				+ node.getClass().getSimpleName(),
				(CclibUnit)classNode.getAttribute("unit"), node.line());
	}
	
	private final void compile() throws CclibException {
		// create class descriptions of the class and it's parent
		final VaClsInfo info = clsInfo;
		final VaClsInfo parent;
		
		if(info.ident.equals("Korona.Object")) {
			// FIXME: struct classes don't have a parent
			parent = null;
		}else if(classNode.getParent() == null) {
			parent = new VaClsInfo("Korona.Object", 0, 0, 0, 0);
		}else{
			// resolve the parent
			final VaDatatype.DtClass clstype = (VaDatatype.DtClass)typeResolve(classNode.getParent());
			
			parent = clstype.clsInfo;
		}
		
		// create arrays for methods and fields
		final VaMthInfo[] methods = new VaMthInfo[classNode.getAttrInt("NumberOfMethods")];
		final VaFldInfo[] fields = new VaFldInfo[classNode.getAttrInt("NumberOfFields")];

		// add method descriptions to this class
		for(int j = 0; j < classNode.getAttrInt("NumberOfFields"); j++) {
			final KorAstNode node = classNode.getChild("Field" + j);
			final AstField fld = (AstField)node;
			final VaFldInfo fldinfo = describeField(fld);
			fields[j] = fldinfo;
		}

		// add method descriptions to this class
		for(int j = 0; j < classNode.getAttrInt("NumberOfMethods"); j++) {
			methods[j] = describeMethod(classNode.getChild("Method" + j));
		}
		
		virtClass = new VaClass(info, parent, methods, fields);
	}
	
	private final VaDatatype resolveSymbol(final VaDatatype type,
			final AstSymbol symbol) throws CclibException {
		if(symbol instanceof AstArray) {
			// create an array type
			final VaDatatype.DtArray subtype = new VaDatatype.DtArray(type, 0);
			
			// resolve sub symbols
			if(symbol.getChild() != null)
				return resolveSymbol(subtype, symbol.getChild());
			
			// return the array type
			return subtype;
		}else{
			throw new CclibException("Compile error: Could not resolve "
					+ symbol.getClass().getSimpleName(),
					(CclibUnit)classNode.getAttribute("unit"), symbol.line());
		}
	}
	
	private final VaDatatype resolveClass(final KorAstNode node,
			final AstSpace parent) throws CclibException, KorUtils.UnresolvedException {
		// start with the parent node
		AstSpace space = parent;
		AstSymbol symbol = (AstSymbol)node;
		
		while(true) {
			// get the symbol's child
			final AstSymbol next = symbol.getChild();
			
			if(symbol instanceof AstTerm) {
				final AstTerm subterm = (AstTerm)symbol;
				
				// get the child node
				final KorAstNode child = space.getSpaceEntry(subterm.getIdentifier());
				
				if(child == null) {
					// if we could not resolve the first part of the symbol
					// throw an unresolvable exception
					if(symbol == node)
						throw new KorUtils.UnresolvedException();
					
					// otherwise throw a compiler error
					throw new CclibException("Compile error: " +
							"Could not resolve " + subterm.getIdentifier(),
							(CclibUnit)classNode.getAttribute("unit"),
							subterm.line());
				}
				
				if(child instanceof AstSpace) {
					/* the symbol must have a child <= the last element of
					 * the symbol must not be a namespace */
					if(next == null)
						throw new CclibException("Compile error: "
								+ subterm.getIdentifier()
								+ " is not a class but a namespace",
								(CclibUnit)classNode.getAttribute("unit"),
								symbol.line());
										
					// continue with the child space
					space = (AstSpace)child;
					
					// continue with the child symbol
					symbol = symbol.getChild();
				}else if(child instanceof AstClass) {
					// convert the child to an ast class node
					final AstClass cls = (AstClass)child;
					
					// create a class type
					final VaDatatype type = new VaDatatype.DtClass(imageCmp.lookupClass(cls).getClsInfo());
					
					// compile sub symbolss
					if(next != null)
						return resolveSymbol(type, next);
					
					// return the type
					return type;
				}else if(child instanceof AstGeneric) {
					// next next simple must not be null
					if(next == null)
						throw new CclibException("Compile error: "
								+ subterm.getIdentifier() 
								+ " is a generic class",
								(CclibUnit)classNode.getAttribute("unit"),
								symbol.line());
					
					// the next symbol must be a generic instance
					if(next instanceof AstInstance == false)
						throw new CclibException("Compile error: "
								+ subterm.getIdentifier()
								+ " is a generic class",
								(CclibUnit)classNode.getAttribute("unit"),
								symbol.line());
					
					// merge the generic instance
					VaDatatype type = new VaDatatype.DtClass(imageCmp.mergeGeneric(this, (AstGeneric)child,
							(AstInstance)next).getClsInfo());
					
					// compile sub symbolss
					if(next.getChild() != null)
						return resolveSymbol(type, next.getChild());
					
					// return the type
					return type;
				}else{
					throw new CclibException("Internal error: Unknown ast node "
							+ child.getClass().getSimpleName(),
							(CclibUnit)classNode.getAttribute("unit"),
							child.line());
				}
			}else{
				throw new CclibException("Internal error: Unknown symbol type "
						+ symbol.getClass().getSimpleName(),
						(CclibUnit)classNode.getAttribute("unit"),
						symbol.line());
			}
		}
	}
	
	// creates a class description
	private final VaClsInfo describeClass() {
		StringBuffer ident = new StringBuffer(classNode.getPath());
		
		// loop through the entries of the pattern map
		final Iterator<VaDatatype> iter = patternList.iterator();
		while(iter.hasNext()) {
			// get the next entry
			final VaDatatype entry = iter.next();
			
			ident.append('[');
			ident.append(entry.toString());
			ident.append(']');
		}
		
		// process the flags
		int flags = 0;
		if((classNode.getModifiers() & AstClass.MOD_STRUCT) != 0)
			flags |= VaClsInfo.FLAG0_STRUCT;
		if((classNode.getModifiers() & AstClass.MOD_FINAL) != 0)
			flags |= VaClsInfo.FLAG0_FINAL;
		if(ident.toString().equals("ManaVaLib.DtArray"))
			System.nanoTime();
		return new VaClsInfo(ident.toString(), 0, 0, 0, flags);
	}
	
	// creates a method description
	public final VaMthInfo describeMethod(KorAstNode node) throws CclibException {
		// get the argument count
		final int argcnt = node.getAttrInt("NumberOfArguments");
		
		// resolve the argument types
		final VaDatatype[] args = new VaDatatype[argcnt];
		for(int i = 0; i < argcnt; i++) {
			args[i] = typeResolve(node.getChild("ArgumentType" + i));
		}
		
		// resolve the flags
		int flags = 0;
		if(node.attrBoolIsSet("FlagStatic"))
			flags |= VaMthInfo.FLAG0_STATIC;
		if(node.attrBoolIsSet("FlagAbstract"))
			flags |= VaMthInfo.FLAG0_ABSTRACT;
		if(node.attrBoolIsSet("FlagNative"))
			flags |= VaMthInfo.FLAG0_NATIVE;
		if(node.attrBoolIsSet("FlagFinal"))
			flags |= VaMthInfo.FLAG0_FINAL;

		// resolve the return type
		VaDatatype type = new VaDatatype.DtPrimitive
				(VaDatatype.DtPrimitive.PRIM_NULL);
		if(node.attrBoolIsSet("FlagConstructor") == false)
			type = typeResolve(node.getChild("ReturnType"));
		
		return new VaMthInfo(node.getAttrStr("MethodName"),
				clsInfo, type, args, flags);
	}
	
	// creates a field description
	private final VaFldInfo describeField(AstField fld) throws CclibException {
		final VaDatatype type = typeResolve(fld.getType());
		
		boolean is_static = (fld.getFlags() & AstField.FLAG_STATIC) != 0;
		
		// literal of this field
		VaLiteral literal = null;
		
		if(KorUtils.isLiteral(fld.getExpression()))
			literal = KorUtils.buildLiteral(fld.getExpression());
		
		if(is_static)
			return new VaFldInfo(fld.getName(), clsInfo, VaFldInfo.FLAG0_STATIC, type, literal);
		return new VaFldInfo(fld.getName(), clsInfo, 0, type, literal);
		
	}
}
